package controllers

import (
	"com.opennews.openplatform/web_api/services"
	"com.opennews.openplatform/web_api/shared"
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
	"time"
)

// Sets routes for user resource.
func SetUserRoutes(routerGroup *gin.RouterGroup) {
	controller := NewUserController()

	routerGroup.GET("/get", controller.List)
	routerGroup.GET("/get/:id", controller.GetById)
	routerGroup.GET("/birthday/:year/:month", controller.GetByYearAndMonth)
	routerGroup.GET("/query", controller.Query)
	routerGroup.POST("/", controller.Create)
	routerGroup.PUT("/", controller.Update)
	routerGroup.DELETE("/:id", controller.Delete)
}

type UserController interface {
	List(ctx *gin.Context)
	GetById(ctx *gin.Context)
	GetByYearAndMonth(ctx *gin.Context)
	Query(ctx *gin.Context)
	Create(ctx *gin.Context)
	Update(ctx *gin.Context)
	Delete(ctx *gin.Context)
}

// Returns the instance of private member, userController, which implements UserController interface.
func NewUserController() UserController {
	return userController{}
}

// Matches /get of http get action.
func (this userController) List(ctx *gin.Context) {
	this.userService = services.NewUserService()
	users := this.userService.List()

	// Responses with specified http status code and json format result.
	SendDataResponse(ctx, users)
}

// Matches /get/ea3af20a-a0fc-4595-b575-43a4ce478f1d of http get action.
// ea3af20a-a0fc-4595-b575-43a4ce478f1d is example of user id.
func (this userController) GetById(ctx *gin.Context) {
	this.userService = services.NewUserService()

	// Gets the parameters from path.
	id := ctx.Param("id")
	result := this.userService.GetById(id)

	// Responses with specified http status code and json format result.
	SendDataResponse(ctx, result)
}

// Matches /birthday/1982/10 of http get action.
func (this userController) GetByYearAndMonth(ctx *gin.Context) {
	this.userService = services.NewUserService()

	// Gets the parameters from path.
	year, _ := shared.StringToInt(ctx.Param("year"))
	month, _ := shared.StringToInt(ctx.Param("month"))

	// Prepares the message based on parameters.
	message := fmt.Sprintf("year: %v, month: %v", year, month)

	// Calls function in service.
	this.userService.GetByYearAndMonth(year, month)

	// Responses with specified http status code and json format result.
	SendDataResponse(ctx, shared.MapData{"message": message})
}

// Matches / of http get action.
// You can add more query parameters as you need.
func (this userController) Query(ctx *gin.Context) {
	// Gets the parameters from query string.
	username := ctx.Query("username")
	level := ctx.Query("level")
	birthday := ctx.Query("birthday")

	// Prepares the message based on parameters.
	message := fmt.Sprintf("%v with level %v and birthday %v", username, level, birthday)

	// Responses with specified http status code and json format result.
	SendDataResponse(ctx, shared.MapData{"message": message})
}

// Matches / of http post action.
func (this userController) Create(ctx *gin.Context) {
	// The strong type example.
	// The nested json data also works great with strong type.
	// With strong type data everything works as you would expect.
	//var postedData PostedUser

	// If the posted data structure is not shared by different controller functions you can define it here.
	var postedData struct {
		Username        string    `json:"username"`
		Password        string    `json:"password"`
		Level           int       `json:"level"`
		IsAdmin         bool      `json:"isAdmin"`
		LiteralBirthday string    `json:"birthday"` // Accepts the posted date in string format.
		Birthday        time.Time // Auto binding would fail due to different date format. That's why use LiteralBirthday to accept the date string and parse it manually.
	}

	// Binds data from posted json.
	ctx.BindJSON(&postedData)

	// Parses the date string from LiteralBirthday to date for Birthday.
	postedData.Birthday, _ = time.Parse(shared.LongDateFormat, postedData.LiteralBirthday)

	// Prints the string field.
	println(postedData.Username)

	// Responses with specified http status.
	SendStatusResponse(ctx, http.StatusNoContent)
}

// Matches / of http put action.
func (this userController) Update(ctx *gin.Context) {
	this.userService = services.NewUserService()

	// The dynamic type example.
	// Based on its behavior you might consider to use strong type instead if the struct is complicated.
	putData := gin.H{}

	// Binds data from put json.
	// Remember the data type is map[string]interface{}.
	// That's why you cannot access its key-value as you might expect.
	ctx.BindJSON(&putData)

	// department is a nested field which its type actually is interface{}. We need to assert its type to map[string]interface{} explicitly.
	// Otherwise you cannot access it data via key.
	// department := putData["department"].(map[string]interface{})

	// The department["name"]' type is also interface{}. We still need to assert it to string type.
	// department["name"].(string)

	// The way to assert the nested list type.
	//roles := putData["roles"].([]interface{})
	//role := roles[0].(map[string]interface{})
	//println(role["name"].(string))

	// Responses with specified http status code.
	SendDataResponse(ctx, shared.MapData{"message": putData})
}

// Matches /ea3af20a-a0fc-4595-b575-43a4ce478f1d of http delete action.
func (this userController) Delete(ctx *gin.Context) {
	this.userService = services.NewUserService()

	// Gets the parameters from path.
	id := ctx.Param("id")

	this.userService.Delete(id)

	// Responses with specified http status.
	SendStatusResponse(ctx, http.StatusNoContent)
}

// userController is a container which holds functions like List, GetBy and so on.
// This can make NO conflict with other same name functions which attached to other controllers.
type userController struct {
	userService services.UserService
}

// region: Example for BindJSON function if you prefer strong type which can be shared cross different controller functions.

type PostedUser struct {
	Username        string           `json:"username"`
	Password        string           `json:"password"`
	Level           int              `json:"level"`
	IsAdmin         bool             `json:"isAdmin"`
	LiteralBirthday string           `json:"birthday"` // Accepts the posted date in string format.
	Department      PostedDepartment `json:"department"`
	Roles           []PostedRole     `json:"roles"`
	Birthday        time.Time        // Auto binding would fail due to different date format. That's why use LiteralBirthday to accept the date string and parse it manually.
}

type PostedDepartment struct {
	Id   string `json:"id"`
	Name string `json:"name"`
}

type PostedRole struct {
	Id   string `json:"id"`
	Name string `json:"name"`
}

// endregion
